package fr.sii.ogham.helper.email; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; import javax.mail.Address; import javax.mail.BodyPart; import javax.mail.Message; import javax.mail.Message.RecipientType; import javax.mail.MessagingException; import javax.mail.Multipart; import javax.mail.Part; import org.apache.commons.io.IOUtils; import org.junit.Assert; import org.junit.ComparisonFailure; import fr.sii.ogham.helper.html.AssertHtml; /** * Utility class for checking if the received email content is as expected. * * @author Aurélien Baudet * */ public class AssertEmail { private static final Pattern HTML_PATTERN = Pattern.compile("<html", Pattern.CASE_INSENSITIVE); private static final Pattern TEXT_OR_HTML_MIMETYPES = Pattern.compile("^((text/)|(application/x?html)).*", Pattern.CASE_INSENSITIVE); /** * Assert that the fields of the received email are equal to the expected * values. The expected email contains several parts (several contents). The * received email should also have the equivalent parts. It will check that: * <ul> * <li>The received headers are respected (see * {@link #assertHeaders(ExpectedEmailHeader, Message)})</li> * <li>The received message is a {@link Multipart} email</li> * <li>The number of parts are equal</li> * <li>Each received part body equals the expected one (order is important). * See {@link #assertBody(String, String, boolean)}</li> * <li>Each received part Mime Type equals the expected one (order is * important). See {@link #assertMimetype(ExpectedContent, String)}</li> * </ul> * <p> * The checking of the body is done strictly (totally equal). * </p> * * @param expectedEmail * all the fields with their expected values * @param actualEmail * the received email * @throws MessagingException * when accessing the received email fails * @throws IOException * when reading the content of the email fails */ public static void assertEquals(ExpectedMultiPartEmail expectedEmail, Message actualEmail) throws MessagingException, IOException { assertEquals(expectedEmail, actualEmail, true); } /** * <p> * Shortcut to simplify unit testing with GreenMail. See * {@link #assertEquals(ExpectedMultiPartEmail[], Message[])}. * </p> * Assert that the fields of the received email are equal to the expected * values. The expected email contains several parts (several contents). The * received email should also have the equivalent parts. It will check that: * <ul> * <li>The received headers are respected (see * {@link #assertHeaders(ExpectedEmailHeader, Message)})</li> * <li>The received message is a {@link Multipart} email</li> * <li>The number of parts are equal</li> * <li>Each received part body equals the expected one (order is important). * See {@link #assertBody(String, String, boolean)}</li> * <li>Each received part Mime Type equals the expected one (order is * important). See {@link #assertMimetype(ExpectedContent, String)}</li> * </ul> * <p> * The checking of the body is done strictly (totally equal). * </p> * * @param expectedEmail * all the fields with their expected values * @param actualEmails * the received email * @throws MessagingException * when accessing the received email fails * @throws IOException * when reading the content of the email fails */ public static void assertEquals(ExpectedMultiPartEmail expectedEmail, Message[] actualEmails) throws MessagingException, IOException { assertEquals(new ExpectedMultiPartEmail[] { expectedEmail }, actualEmails); } /** * Assert that each received email content respects the expected one. It * ensures that the number of received emails equals to the expected number. * Then for each email it calls * {@link #assertEquals(ExpectedMultiPartEmail, Message)} . * * @param expectedEmails * the list of expected emails * @param actualEmails * the received emails * @throws MessagingException * when accessing the received email fails * @throws IOException * when reading the content of the email fails */ public static void assertEquals(ExpectedMultiPartEmail[] expectedEmails, Message[] actualEmails) throws MessagingException, IOException { Assert.assertEquals("should have " + expectedEmails.length + " email", expectedEmails.length, actualEmails.length); for (int i = 0; i < expectedEmails.length; i++) { assertEquals(expectedEmails[i], actualEmails[i]); } } /** * <p> * Shortcut to simplify unit testing with GreenMail. See * {@link #assertEquals(ExpectedEmail[], Message[])}. * </p> * Assert that the fields of the received email are equal to the expected * values. The expected email contains only one part (only one content). It * will check that: * <ul> * <li>The received headers are respected (see * {@link #assertHeaders(ExpectedEmailHeader, Message)})</li> * <li>The body equals the expected one (order is important). See * {@link #assertBody(String, String, boolean)}</li> * <li>The Mime Type equals the expected one (order is important). See * {@link #assertMimetype(ExpectedContent, String)}</li> * </ul> * <p> * The checking of the body is done strictly (totally equal). * </p> * * * @param expectedEmail * all the fields with their expected values * @param actualEmails * the received emails * @throws MessagingException * when accessing the received email fails */ public static void assertEquals(ExpectedEmail expectedEmail, Message[] actualEmails) throws MessagingException { assertEquals(new ExpectedEmail[] { expectedEmail }, actualEmails); } /** * Assert that each received email content respects the expected one. It * ensures that the number of received emails equals to the expected number. * Then for each email it calls * {@link #assertEquals(ExpectedEmail, Message)} . * * @param expectedEmail * the expected email * @param actualEmails * the received emails * @throws MessagingException * when accessing the received email fails */ public static void assertEquals(ExpectedEmail[] expectedEmail, Message[] actualEmails) throws MessagingException { Assert.assertEquals("should have " + expectedEmail.length + " email", expectedEmail.length, actualEmails.length); for (int i = 0; i < expectedEmail.length; i++) { assertEquals(expectedEmail[i], actualEmails[i]); } } /** * Assert that the fields of the received email are equal to the expected * values. The expected email contains only one part (only one content). It * will check that: * <ul> * <li>The received headers are respected (see * {@link #assertHeaders(ExpectedEmailHeader, Message)})</li> * <li>The body equals the expected one (order is important). See * {@link #assertBody(String, String, boolean)}</li> * <li>The Mime Type equals the expected one (order is important). See * {@link #assertMimetype(ExpectedContent, String)}</li> * </ul> * <p> * The checking of the body is done strictly (totally equal). * </p> * * @param expectedEmail * all the fields with their expected values * @param actualEmail * the received email * @throws MessagingException * when accessing the received email fails */ public static void assertEquals(ExpectedEmail expectedEmail, Message actualEmail) throws MessagingException { assertEquals(expectedEmail, actualEmail, true); } /** * Assert that the fields of the received email are equal to the expected * values. The expected email contains several parts (several contents). The * received email should also have the equivalent parts. It will check that: * <ul> * <li>The received headers are respected (see * {@link #assertHeaders(ExpectedEmailHeader, Message)})</li> * <li>The received message is a {@link Multipart} email</li> * <li>The number of parts are equal</li> * <li>Each received part body equals the expected one (order is important). * See {@link #assertBody(String, String, boolean)}</li> * <li>Each received part Mime Type equals the expected one (order is * important). See {@link #assertMimetype(ExpectedContent, String)}</li> * </ul> * <p> * The checking of the body ignores the new line characters. * </p> * * @param expectedEmail * all the fields with their expected values * @param actualEmail * the received email * @throws MessagingException * when accessing the received email fails * @throws IOException * when reading the content of the email fails */ public static void assertSimilar(ExpectedMultiPartEmail expectedEmail, Message actualEmail) throws MessagingException, IOException { assertEquals(expectedEmail, actualEmail, false); } /** * <p> * Shortcut to simplify unit testing with GreenMail. See * {@link #assertSimilar(ExpectedMultiPartEmail[], Message[])}. * </p> * Assert that the fields of the received email are equal to the expected * values. The expected email contains several parts (several contents). The * received email should also have the equivalent parts. It will check that: * <ul> * <li>The received headers are respected (see * {@link #assertHeaders(ExpectedEmailHeader, Message)})</li> * <li>The received message is a {@link Multipart} email</li> * <li>The number of parts are equal</li> * <li>Each received part body equals the expected one (order is important). * See {@link #assertBody(String, String, boolean)}</li> * <li>Each received part Mime Type equals the expected one (order is * important). See {@link #assertMimetype(ExpectedContent, String)}</li> * </ul> * <p> * The checking of the body ignores the new line characters. * </p> * * @param expectedEmail * all the fields with their expected values * @param actualEmails * the received email * @throws MessagingException * when accessing the received email fails * @throws IOException * when reading the content of the email fails */ public static void assertSimilar(ExpectedMultiPartEmail expectedEmail, Message[] actualEmails) throws MessagingException, IOException { assertSimilar(new ExpectedMultiPartEmail[] { expectedEmail }, actualEmails); } /** * Assert that each received email content respects the expected one. It * ensures that the number of received emails equals to the expected number. * Then for each email it calls * {@link #assertSimilar(ExpectedMultiPartEmail, Message)} . * * @param expectedEmails * the list of expected emails * @param actualEmails * the received emails * @throws MessagingException * when accessing the received email fails * @throws IOException * when reading the content of the email fails */ public static void assertSimilar(ExpectedMultiPartEmail[] expectedEmails, Message[] actualEmails) throws MessagingException, IOException { Assert.assertEquals("should have " + expectedEmails.length + " email", expectedEmails.length, actualEmails.length); for (int i = 0; i < expectedEmails.length; i++) { assertSimilar(expectedEmails[i], actualEmails[i]); } } /** * <p> * Shortcut to simplify unit testing with GreenMail. See * {@link #assertSimilar(ExpectedEmail[], Message[])}. * </p> * Assert that the fields of the received email are equal to the expected * values. The expected email contains only one part (only one content). It * will check that: * <ul> * <li>The received headers are respected (see * {@link #assertHeaders(ExpectedEmailHeader, Message)})</li> * <li>The body equals the expected one (order is important). See * {@link #assertBody(String, String, boolean)}</li> * <li>The Mime Type equals the expected one (order is important). See * {@link #assertMimetype(ExpectedContent, String)}</li> * </ul> * <p> * The checking of the body ignores the new line characters. * </p> * * * @param expectedEmail * all the fields with their expected values * @param actualEmails * the received emails * @throws MessagingException * when accessing the received email fails */ public static void assertSimilar(ExpectedEmail expectedEmail, Message[] actualEmails) throws MessagingException { assertSimilar(new ExpectedEmail[] { expectedEmail }, actualEmails); } /** * Assert that each received email content respects the expected one. It * ensures that the number of received emails equals to the expected number. * Then for each email it calls * {@link #assertSimilar(ExpectedEmail, Message)} . * * @param expectedEmail * the expected email * @param actualEmails * the received emails * @throws MessagingException * when accessing the received email fails */ public static void assertSimilar(ExpectedEmail[] expectedEmail, Message[] actualEmails) throws MessagingException { Assert.assertEquals("should have " + expectedEmail.length + " email", expectedEmail.length, actualEmails.length); for (int i = 0; i < expectedEmail.length; i++) { assertSimilar(expectedEmail[i], actualEmails[i]); } } /** * Assert that the fields of the received email are equal to the expected * values. The expected email contains only one part (only one content). It * will check that: * <ul> * <li>The received headers are respected (see * {@link #assertHeaders(ExpectedEmailHeader, Message)})</li> * <li>The body equals the expected one (order is important). See * {@link #assertBody(String, String, boolean)}</li> * <li>The Mime Type equals the expected one (order is important). See * {@link #assertMimetype(ExpectedContent, String)}</li> * </ul> * <p> * The checking of the body ignores the new line characters. * </p> * * @param expectedEmail * all the fields with their expected values * @param actualEmail * the received email * @throws MessagingException * when accessing the received email fails */ public static void assertSimilar(ExpectedEmail expectedEmail, Message actualEmail) throws MessagingException { assertEquals(expectedEmail, actualEmail, false); } /** * Assert that the fields of the received email are equal to the expected * values. The expected email contains several parts (several contents). The * received email should also have the equivalent parts. It will check that: * <ul> * <li>The received headers are respected (see * {@link #assertHeaders(ExpectedEmailHeader, Message)})</li> * <li>The received message is a {@link Multipart} email</li> * <li>The number of parts are equal</li> * <li>Each received part body equals the expected one (order is important). * See {@link #assertBody(String, String, boolean)}</li> * <li>Each received part Mime Type equals the expected one (order is * important). See {@link #assertMimetype(ExpectedContent, String)}</li> * </ul> * <p> * The checking of the body may be done either strictly (totally equal) or * not (new line characters are ignored). * </p> * * @param expectedEmail * all the fields with their expected values * @param actualEmail * the received email * @param strict * true for strict checking (totally equals) or false to ignore * new line characters in body contents * @throws MessagingException * when accessing the received email fails * @throws IOException * when reading the content of the email fails */ public static void assertEquals(ExpectedMultiPartEmail expectedEmail, Message actualEmail, boolean strict) throws MessagingException, IOException { assertHeaders(expectedEmail, actualEmail); Object content = actualEmail.getContent(); Assert.assertTrue("should be multipart message", content instanceof Multipart); List<Part> bodyParts = getBodyParts(actualEmail); Assert.assertEquals("should have " + expectedEmail.getExpectedContents().length + " parts", expectedEmail.getExpectedContents().length, bodyParts.size()); for (int i = 0; i < expectedEmail.getExpectedContents().length; i++) { assertBody(expectedEmail.getExpectedContents()[i].getBody(), bodyParts.get(i).getContent().toString(), strict); assertMimetype(expectedEmail.getExpectedContents()[i], bodyParts.get(i).getContentType()); } } /** * Assert that the fields of the received email are equal to the expected * values. The expected email contains only one part (only one content). It * will check that: * <ul> * <li>The received headers are respected (see * {@link #assertHeaders(ExpectedEmailHeader, Message)})</li> * <li>The body equals the expected one (order is important). See * {@link #assertBody(String, String, boolean)}</li> * <li>The Mime Type equals the expected one (order is important). See * {@link #assertMimetype(ExpectedContent, String)}</li> * </ul> * <p> * The checking of the body may be done either strictly (totally equal) or * not (new line characters are ignored). * </p> * * @param expectedEmail * all the fields with their expected values * @param actualEmail * the received email * @param strict * true for strict checking (totally equals) or false to ignore * new line characters in body contents * @throws MessagingException * when accessing the received email fails */ public static void assertEquals(ExpectedEmail expectedEmail, Message actualEmail, boolean strict) throws MessagingException { assertHeaders(expectedEmail, actualEmail); assertBody(expectedEmail.getExpectedContent().getBody(), getBody(actualEmail), strict); assertMimetype(expectedEmail.getExpectedContent(), getBodyMimetype(actualEmail)); } /** * Checks that the received Mime Type for the message is like the expected * Mime Type. The expected Mime Type is a regular expression. * * @param expectedContent * the expected email content that contains the expected Mime * Type as regular expression * @param actualEmail * the received email to check * @throws MessagingException * when accessing the received email fails */ public static void assertMimetype(ExpectedContent expectedContent, Message actualEmail) throws MessagingException { assertMimetype(expectedContent, actualEmail.getContentType()); } /** * Checks that the received Mime Type for the message is like the expected * Mime Type. The expected Mime Type is a regular expression. * * @param expectedContent * the expected email content that contains the expected Mime * Type as regular expression * @param contentType * the received email Mime Type * @throws MessagingException * when accessing the received email fails */ private static void assertMimetype(ExpectedContent expectedContent, String contentType) throws MessagingException { Assert.assertTrue("mimetype should be " + expectedContent.getMimetype() + " instead of " + contentType, expectedContent.getMimetype().matcher(contentType).matches()); } /** * Checks if the received body equals the expected body. It handles HTML * content and pure text content. * <p> * For text, the check can be done either strictly (totally equal) or not * (ignore new lines). * </p> * <p> * For HTML, the string content is parsed and DOM trees are compared. The * comparison can be done either strictly (DOM trees are totally equals, * attributes are in the same order) or not (attributes can be in any * order). * </p> * * @param expectedBody * the expected content as string * @param actualBody * the received content as string * @param strict * true for strict checking (totally equals) or false to ignore * new line characters */ private static void assertBody(String expectedBody, String actualBody, boolean strict) { if (isHtml(expectedBody)) { if (strict) { AssertHtml.assertIdentical(expectedBody, actualBody); } else { AssertHtml.assertSimilar(expectedBody, actualBody); } } else { if(strict ? !expectedBody.equals(actualBody) : !sanitize(expectedBody).equals(sanitize(actualBody))) { throw new ComparisonFailure("body should be '" + expectedBody + "'", expectedBody, actualBody); } } } /** * Checks if the received headers corresponds to the expected header values. * It checks if: * <ul> * <li>The received subject equals the expected subject</li> * <li>The email sender address equals the expected sender address</li> * <li>The total number of recipients equals the total number of expected * recipients</li> * <li>The number of "to" recipients equals the number of expected "to" * recipients</li> * <li>The number of "cc" recipients equals the number of expected "cc" * recipients</li> * <li>The number of "bcc" recipients equals the number of expected "bcc" * recipients</li> * <li>Each "to" recipient equals the expected "to" recipient value</li> * <li>Each "cc" recipient equals the expected "cc" recipient value</li> * <li>Each "bcc" recipient equals the expected "bcc" recipient value</li> * </ul> * * @param expectedEmail * the expected header values * @param actualEmail * the received header values * @throws MessagingException * when accessing the received email fails */ private static void assertHeaders(ExpectedEmailHeader expectedEmail, Message actualEmail) throws MessagingException { Assert.assertEquals("subject should be '" + expectedEmail.getSubject() + "'", expectedEmail.getSubject(), actualEmail.getSubject()); Assert.assertEquals("should have only one from", 1, actualEmail.getFrom().length); Assert.assertEquals("from should be '" + expectedEmail.getFrom() + "'", expectedEmail.getFrom(), actualEmail.getFrom()[0].toString()); int recipients = expectedEmail.getTo().size() + expectedEmail.getBcc().size() + expectedEmail.getCc().size(); Assert.assertEquals("should have only " + recipients + " recipients", recipients, actualEmail.getAllRecipients().length); assertRecipients(expectedEmail.getTo(), actualEmail, RecipientType.TO); assertRecipients(expectedEmail.getCc(), actualEmail, RecipientType.CC); assertRecipients(expectedEmail.getBcc(), actualEmail, RecipientType.BCC); } /** * Checks if the received headers corresponds to the expected header values. * It checks if: * <ul> * <li>The number of recipients of the provided type equals the number of * expected recipients of the provided type</li> * <li>Each recipient of the provided type equals the expected recipient * value of the provided type</li> * </ul> * * @param expectedRecipients * the list of recipient string values * @param actualEmail * the received header values * @param recipientType * the type of the recipient to compare * @throws MessagingException * when accessing the received email fails */ private static void assertRecipients(List<String> expectedRecipients, Message actualEmail, RecipientType recipientType) throws MessagingException { Address[] actualRecipients = actualEmail.getRecipients(recipientType); if (expectedRecipients.isEmpty()) { Assert.assertTrue("should have no recipients " + recipientType, actualRecipients == null || actualRecipients.length == 0); } else { Assert.assertEquals("should have only " + expectedRecipients.size() + " " + recipientType, expectedRecipients.size(), actualRecipients.length); for (int i = 0; i < expectedRecipients.size(); i++) { Assert.assertEquals(recipientType + "[" + i + "] should be '" + expectedRecipients.get(i) + "'", expectedRecipients.get(i), actualRecipients[i].toString()); } } } /** * Remove new lines from the string. * * @param str * the string to sanitize * @return the sanitized string */ private static String sanitize(String str) { return str.replaceAll("\r|\n", ""); } public static Part getBodyPart(Part actualEmail) throws MessagingException { List<Part> bodyParts = getBodyParts(actualEmail); if(bodyParts.isEmpty()) { throw new IllegalStateException("Expected at least one body part but none found"); } return bodyParts.get(0); } public static List<Part> getBodyParts(Part actualEmail) throws MessagingException { List<Part> founds = new ArrayList<>(); getBodyParts(actualEmail, founds); return founds; } public static void getBodyParts(Part actualEmail, List<Part> founds) throws MessagingException { try { Object content = actualEmail.getContent(); if (content instanceof Multipart) { Multipart mp = (Multipart) content; for(int i=0 ; i<mp.getCount() ; i++) { BodyPart part = mp.getBodyPart(i); if(part.getContentType().startsWith("multipart/")) { getBodyParts(part, founds); } else if(TEXT_OR_HTML_MIMETYPES.matcher(part.getContentType()).matches()){ founds.add(part); } } } } catch (IOException e) { throw new MessagingException("Failed to access content of the mail", e); } } public static String getBody(Part actualEmail) throws MessagingException { try { Object content = getBodyPart(actualEmail).getContent(); if(content instanceof String) { return (String) content; } else if(content instanceof InputStream) { return IOUtils.toString((InputStream) content); } else { return content.toString(); } } catch (IOException e) { throw new MessagingException("Failed to access content of the mail", e); } } public static String getBodyMimetype(Part actualEmail) throws MessagingException { return getBodyPart(actualEmail).getContentType(); } private static boolean isHtml(String expectedBody) { return HTML_PATTERN.matcher(expectedBody).find(); } private AssertEmail() { super(); } }